<script lang="ts">
import { ref, reactive, defineComponent, type PropType, watch, inject, computed } from 'vue'
import { createFileItem, type UploadFile } from '../x-upload-file/index.vue'
import XIcon from '../x-icon/index.vue'
import { ElImageViewer } from 'element-plus'

export default defineComponent({
	name: 'x-upload-image',
})
</script>
<script setup lang="ts">
const props = defineProps({
	modelValue: {
		type: [Array, String],
		default: '',
	},
	/** 图片数量，0表示不限制 */
	count: {
		type: Number,
		default: 1,
	},
	imageStyle: {
		type: Object,
		default: () => ({
			width: '100px',
			height: '100px',
		}),
	},
	circleSize: {
		type: Number,
		default: 80,
	},
	iconSize: {
		type: [Number, String],
		default: 24,
	},
	objectFit: {
		type: String as PropType<'fill' | 'contain' | 'cover' | 'none' | 'scale-down'>,
		default: 'cover',
	},
	accept: {
		type: String,
		default: 'image/*',
	},
	/**
	 * 文件上传的api
	 * 可通过 provide('x-upload-image:defaultApi') 全局提供默认值
	 */
	api: {
		type: Function as PropType<(file: UploadFile) => Promise<string>>,
	},
})

const emit = defineEmits(['update:modelValue'])

const file_list = ref<UploadFile[]>([])
const preview = reactive({
	show: false,
	list: computed(() => file_list.value.map((i) => i.url)),
})

const injectDefaultApi = inject('x-upload-image:defaultApi', undefined)

const setFileList = () => {
	if (props.count == 1) {
		if (typeof props.modelValue != 'string') {
			console.error(
				'[x-upload-image] props.modelValue except be "string" when props.count is 1, but got ' +
					typeof props.modelValue
			)
			return
		}
		// 值一致的情况避免循环调用
		if (file_list.value.length == 1 && file_list.value[0].url == props.modelValue) return
		file_list.value = props.modelValue
			? [createFileItem({ url: props.modelValue, status: 'local' }, 'x-upload-image')]
			: []
	} else {
		if (!Array.isArray(props.modelValue)) {
			console.error(
				'[x-upload-image] props.modelValue except be "array" when props.count is not 1, but got ' +
					typeof props.modelValue
			)
			return
		}
		// 值一致的情况避免循环调用
		for (let i = 0; i < props.modelValue.length; i++) {
			if (file_list.value[i] && file_list.value[i].url == props.modelValue[i]) continue
			file_list.value[i] = createFileItem({ url: props.modelValue[i], status: 'local' }, 'x-upload-image')
		}
		file_list.value = file_list.value.slice(0, props.modelValue.length)
	}
}

const updateModelValue = () => {
	let value = '' as any
	if (props.count == 1) {
		value = file_list.value[0]?.url ?? ''
	} else {
		value = file_list.value.map((it) => it.url)
	}
	emit('update:modelValue', value)
}

const handleUpload = (file: File) => {
	const fileItem = createFileItem({ status: 'uploading', file }, 'x-upload-image')
	file_list.value.push(fileItem)

	const upload = props.api ?? injectDefaultApi
	if (upload)
		upload(fileItem)
			.then((url) => {
				fileItem.url = url
				fileItem.percent = 100
				fileItem.status = 'success'
			})
			.catch((e) => {
				fileItem.status = 'failed'
				fileItem.errorMsg = e.message
				deleteImage(file_list.value.findIndex((it) => it.uid == fileItem.uid))
			})
			.finally(() => {
				updateModelValue()
			})
	return false
}

const moveImageItem = (file: UploadFile, index: number, step: number) => {
	if (!file_list.value[index + step]) {
		return
	}
	let old = file_list.value[index]
	file_list.value[index] = file_list.value[index + step]
	file_list.value[index + step] = old

	updateModelValue()
}

const deleteImage = (index) => {
	if (!file_list.value[index]) return

	file_list.value.splice(index, 1)
	updateModelValue()
}

// init流程
watch(
	() => props.modelValue,
	() => setFileList(),
	{ deep: true, immediate: true }
)

defineExpose({ deleteImage })
</script>

<template>
	<div class="x-upload-image">
		<div class="image-item" v-for="(it, index) in file_list" :key="index" :style="imageStyle">
			<el-image
				v-if="['local', 'success'].includes(it.status)"
				class="h-full w-full"
				:src="it.url"
				:fit="objectFit"
			/>
			<el-progress
				v-else
				type="circle"
				:percentage="it.percent"
				:width="circleSize"
				:color="it.status == 'failed' ? '#ff5500' : '#2db7f5'"
			>
				<span class="text-sm">{{ it.status == 'failed' ? it.errorMsg : `${it.percent}%` }}</span>
			</el-progress>
			<div class="mask">
				<XIcon class="icon flex-1" eicon="View" color="#fff" :size="iconSize" @click="preview.show = true" />
				<div v-show="count != 1" class="flex w-full flex-1 items-center justify-between px-1">
					<XIcon
						class="icon"
						eicon="ArrowLeft"
						color="#fff"
						:size="iconSize"
						@click="moveImageItem(it, index, -1)"
					/>
					<XIcon
						class="icon"
						eicon="ArrowRight"
						color="#fff"
						:size="iconSize"
						@click="moveImageItem(it, index, 1)"
					/>
				</div>
				<XIcon class="icon flex-1" eicon="Delete" color="#fff" :size="iconSize" @click="deleteImage(index)" />
			</div>
		</div>
		<el-upload
			v-if="count == 0 || file_list.length < count"
			class="flex flex-shrink-0"
			v-bind="$attrs"
			:accept="accept"
			action=""
			:before-upload="handleUpload"
			:multiple="count != 1"
		>
			<div class="image-item upload-btn" :style="imageStyle">
				<XIcon eicon="Picture" :size="iconSize" />
			</div>
		</el-upload>

		<ElImageViewer v-if="preview.show" :url-list="preview.list" teleported @close="preview.show = false" />
	</div>
</template>
<style lang="less" scoped>
.x-upload-image {
	@apply flex flex-wrap items-center;

	.image-item {
		@apply relative m-1 flex flex-shrink-0 cursor-pointer items-center justify-center rounded;
		border: 1px dashed rgb(184, 184, 184);

		&:hover {
			.mask {
				display: flex;
			}
		}

		.mask {
			@apply absolute left-0 top-0 right-0 bottom-0 flex flex-col items-center;
			display: none;
			background: rgba(0, 0, 0, 0.2);
		}
	}

	.upload-btn {
		&:hover {
			border-color: #129bdf;
			color: #129bdf;
		}
	}
}
</style>
